/* kadu-history-merge -- Simple code, which merges two directories of GG linux IM history into one.
   Copyright (C) 2007 braciszek@gmail.com

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. */

#define _GNU_SOURCE

#include <stdio.h>
#include <getopt.h>
#include <sys/types.h>

#include <sys/dir.h>
#include <sys/param.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#define FALSE 0
#define TRUE !FALSE

#define SYSTEM 0
#define MSGRECV 1
#define MSGSEND 2
#define CHATRECV 3
#define CHATSEND 4
#define STATUS 5


/* Name under which this program was invoked.  */
char *program_name;


/* Nonzero if a non-fatal error has occurred.  */
static int exit_status = 0;

/* Verbose flag, set by -v option. */
static int verbose_flag = 0;

void
usage (int status)
{
  if (status != EXIT_SUCCESS)
    fprintf (stderr, ("Try `%s --help' for more information.\n"),
	     program_name);
  else
    {
      printf (("\
Usage: %s [OPTION]... DIRECTORY1 DIRECTORY2\n\
"), program_name);
      fputs (("\
         Merge history files in dir1 with history files in dir2.\n\
	 Merged history files are saved in directory outdir.\n\
         \n\
         -o DIRECTORY, --output=DIRECTORY      output directory\n\
         \n\
         -s, --ignore-status      ignore status records\n\
         -v, --verbose            be verbose\n\
         \n\
         -h, --help               display this help and exit\n\
         -V, --version            output version information and exit\n\
	     "), stdout);
      printf (("\nReport bugs to <braciszek@gmail.com>.\n"));
    }
  exit (status);
}


void
version (int status)
{
  if (status != EXIT_SUCCESS)
    fprintf (stderr, ("Try `%s --help' for more information.\n"),
	     program_name);
  else
    {
      printf (("\
Kadu history merge 0.1\n\
"));
    }
  exit (status);
}


/* 
 * return true if filename is made from digits
 * almost all gg history files are like that
 * we will reject conference history items
 * return 0 or 1 (TRUE or FALSE)
 **/
int
file_select (const struct direct *entry)
{
  int i;
  /* return FALSE if filename contains non-digit char */
  for (i = 0; i < strlen (entry->d_name); i++)
    {
      if (!isdigit (entry->d_name[i]))
	return (FALSE);
    }

  /* reject "." and ".." entries */
  if ((strcmp (entry->d_name, ".") == 0) ||
      (strcmp (entry->d_name, "..") == 0))
    return (FALSE);
  else
    return (TRUE);
}

/*
 * compare two integers - for qsort
 **/
int
intcmp (const void *v1, const void *v2)
{
  return (*(int *) v1 - *(int *) v2);
}

/*
 * browse directory dir and looks for files with numerical names
 * number of these files will be saved in count
 * table of filenames will be allocated and saved in res 
 * return 1 on success and 0 on failure
 **/
int
listdir (const char *dir, int **res, int *count)
{
  struct direct **files;
  int i;

  /* scan for apprioprate files */
  *count = scandir (dir, &files, file_select, alphasort);

  /* If no files found, return 0 */
  if (*count < 0)
    {
      fprintf (stderr, "Error looking for files in directory %s\n", dir);
      return *count;
    }
  else if (*count == 0)
    {
      fprintf (stderr, "Directory %s doesn't contain history files\n", dir);
      return -1;
    }

  if (verbose_flag == 1)
    printf ("Found %d history files in directory %s\n", *count, dir);

  /* allocate table for file names */
  *res = calloc ((*count) + 1, sizeof (int));

  /* write file names to table */
  for (i = 0; i < *count; ++i)
    {
      (*res)[i] = atoi (files[i]->d_name);
      free (files[i]);
    }
  free (files);

  return *count;
}

/*
 * Extract from one line of history type of item, and timestamp
 * Type of item (msgsend,chatrcv,..) is saved in type
 * Timestamp is saved in time
 * Line of history is provided in line
 * Return 1 on success, 0 on failure
 **/
int
decode (char *line, int *type, int *time)
{
  char *tmp;
  char *tmp2;
  char tmp3[11];
  int i;

  /* Line format is:
   * msgrecv,gg_id,gg_nick,timestamp1,timestamp2,msg_text 
   * ex: msgrecv,4999873,Noga Klem,1123163502,1123155152,Hello !!!
   */
  if (strncmp (line, "msgrecv", 7) == 0)
    {
      /* Find first occurence of ',' after _msgrecv,_ part */
      tmp = strchr (line + 8, ',');
      /* Find first occurence of ',' after gg_nick part, and move forward by 1 char */
      tmp = strchr (tmp + 1, ',') + 1;
      /* Find first occurence of ',' after first timestamp */
      tmp2 = strchr (tmp + 1, ',');
      /* Length of timestamp */
      i = strlen (tmp) - strlen (tmp2);
      /* Copy only timestamp string to tmp3 variable */
      strncpy (tmp3, tmp, i);
      /* Cast to int */
      *time = atoi (tmp3);
      *type = MSGRECV;
    }
  else if (strncmp (line, "msgsend", 7) == 0)
    {
      *type = MSGSEND;
      tmp = strchr (line + 8, ',');
      tmp = strchr (tmp + 1, ',') + 1;
      tmp2 = strchr (tmp + 1, ',');
      i = strlen (tmp) - strlen (tmp2);
      strncpy (tmp3, tmp, i);
      *time = atoi (tmp3);
    }
  else if (strncmp (line, "chatrcv", 7) == 0)
    {
      tmp = strchr (line + 8, ',');
      tmp = strchr (tmp + 1, ',') + 1;
      tmp2 = strchr (tmp + 1, ',');
      i = strlen (tmp) - strlen (tmp2);
      strncpy (tmp3, tmp, i);
      *time = atoi (tmp3);
      *type = CHATRECV;
    }
  else if (strncmp (line, "chatrecv", 8) == 0)
    {
      tmp = strchr (line + 9, ',');
      tmp = strchr (tmp + 1, ',') + 1;
      tmp2 = strchr (tmp + 1, ',');
      i = strlen (tmp) - strlen (tmp2);
      strncpy (tmp3, tmp, i);
      *time = atoi (tmp3);
      *type = CHATRECV;
    }
  else if (strncmp (line, "chatsend", 8) == 0)
    {
      tmp = strchr (line + 9, ',');
      tmp = strchr (tmp + 1, ',') + 1;
      tmp2 = strchr (tmp + 1, ',');
      i = strlen (tmp) - strlen (tmp2);
      strncpy (tmp3, tmp, i);
      *time = atoi (tmp3);
      *type = CHATSEND;
    }
  else if (strncmp (line, "status", 6) == 0)
    {
      tmp = strchr (line + 7, ',');
      tmp = strchr (tmp + 1, ',');
      tmp = strchr (tmp + 1, ',') + 1;
      tmp2 = strchr (tmp + 1, ',');
      i = strlen (tmp) - strlen (tmp2);
      strncpy (tmp3, tmp, i);
      *time = atoi (tmp3);
      *type = STATUS;
    }
  else
    {
      *time = 0;
      *type = SYSTEM;
    }
  return 0;
}


int
mergePair (const int id, const char *dirA, const char *dirB, const char *dirC)
{
  FILE *srcA;
  FILE *srcB;
  FILE *dst;

  char srcA_name[1000];
  char srcB_name[1000];
  char dst_name[1000];
  char *lineA = NULL;
  char *lineB = NULL;
  size_t lenA = 0;
  size_t lenB = 0;
  ssize_t readA = 0;
  ssize_t readB = 0;

  int typeA;
  int typeB;
  int timeA = 0;
  int timeB = 0;

  sprintf (srcA_name, "%s/%d", dirA, id);
  sprintf (srcB_name, "%s/%d", dirB, id);
  sprintf (dst_name, "%s/%d", dirC, id);

  if (verbose_flag == 1)
    printf ("Merging %s/%d with %s/%d into %s/%d ...\n", dirA, id, dirB, id,
	    dirC, id);

  srcA = fopen (srcA_name, "r");
  if (srcA == NULL)
    return -1;

  srcB = fopen (srcB_name, "r");
  if (srcB == NULL)
    return -1;

  dst = fopen (dst_name, "w");
  if (dst == NULL)
    return -1;


  readA = getline (&lineA, &lenA, srcA);
  readB = getline (&lineB, &lenB, srcB);
  decode (lineA, &typeA, &timeA);
  decode (lineB, &typeB, &timeB);

  while ((readA != -1) && (readB != -1))
    {
      if (timeA == timeB)
	{
	  fprintf (dst, "%s", lineA);
	  readA = getline (&lineA, &lenA, srcA);
	  readB = getline (&lineB, &lenB, srcB);
	  decode (lineA, &typeA, &timeA);
	  decode (lineB, &typeB, &timeB);
	}
      else if (timeA < timeB)
	{
	  fprintf (dst, "%s", lineA);
	  readA = getline (&lineA, &lenA, srcA);
	  decode (lineA, &typeA, &timeA);
	}
      else
	{
	  fprintf (dst, "%s", lineB);
	  readB = getline (&lineB, &lenB, srcB);
	  decode (lineB, &typeB, &timeB);
	}
    }
  if (readA == -1)
    while (readB != -1)
      {
	fprintf (dst, "%s", lineB);
	readB = getline (&lineB, &lenB, srcB);
	decode (lineB, &typeB, &timeB);
      }
  if (readB == -1)
    while (readA != -1)
      {
	fprintf (dst, "%s", lineA);
	readA = getline (&lineA, &lenA, srcA);
	decode (lineA, &typeA, &timeA);
      }


  if (lineA)
    free (lineA);
  if (lineB)
    free (lineB);

  fclose (srcA);
  fclose (srcB);
  fclose (dst);

  return 0;
}


int
copyOne (const int id, const char *dirA, const char *dirB)
{
  FILE *src;
  FILE *dst;

  char src_name[1000];
  char dst_name[1000];
  char *line = NULL;
  size_t len = 0;
  ssize_t read;

  if (verbose_flag == 1)
    printf ("Copying %s/%d to %s/%d ...\n", dirA, id, dirB, id);

  sprintf (src_name, "%s/%d", dirA, id);
  sprintf (dst_name, "%s/%d", dirB, id);

  src = fopen (src_name, "r");
  if (src == NULL)
    return -1;

  dst = fopen (dst_name, "w");
  if (dst == NULL)
    return -1;

  while ((read = getline (&line, &len, src)) != -1)
    {
      fprintf (dst, "%s", line);
    }
  if (line)
    free (line);

  fclose (src);
  fclose (dst);

  return 0;
}

int
mergeAll (const int listA[], const int listB[], const int sizeA,
	  const int sizeB, const char *dirA, const char *dirB,
	  const char *dirC)
{
  int i = 0, j = 0;
  while ((i < sizeA) && (j < sizeB))
    {
      if (listA[i] == listB[j])
	{
	  mergePair (listA[i], dirA, dirB, dirC);
	  i++;
	  j++;
	}
      else if (listA[i] < listB[j])
	{
	  copyOne (listA[i], dirA, dirC);
	  i++;
	}
      else if (listA[i] > listB[j])
	{
	  copyOne (listB[j], dirB, dirC);
	  j++;
	}
    }
  if (i == sizeA)
    {
      while (j < sizeB)
	{
	  copyOne (listB[j], dirB, dirC);
	  j++;
	}
    }
  if (j == sizeB)
    {
      while (i < sizeA)
	{
	  copyOne (listA[i], dirA, dirC);
	  i++;
	}
    }
  return 0;
}


int
main (int argc, char *const argv[])
{

  int *l_list;
  int l_list_size;
  int *r_list;
  int r_list_size;

  int c;

  /* Variables that are set according to the specified options.  */
  int ignore_status = 0;
  char *outdir = NULL;

  static struct option const long_options[] = {
    {"output", required_argument, NULL, 'o'},
    {"ignore-status", no_argument, NULL, 's'},
    {"verbose", no_argument, NULL, 'v'},
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'V'},
    {NULL, 0, NULL, 0}
  };

  char *const optstring = "o:svhV";

  program_name = (char *) calloc (strlen (argv[0]) + 1, sizeof (char));
  strcpy (program_name, argv[0]);

  /* Parse command line options.  */
  while ((c = getopt_long (argc, argv, optstring, long_options, NULL)) != -1)
    {
      switch (c)
	{
	case 0:
	  break;

	case 'o':
	  outdir = (char *) calloc (strlen (optarg), sizeof (char));
	  strcpy (outdir, optarg);
	  break;

	case 's':
	  ignore_status = 1;
	  break;

	case 'v':
	  verbose_flag = 1;
	  break;

	case 'h':
	  usage (EXIT_SUCCESS);
	  break;

	case 'V':
	  version (EXIT_SUCCESS);
	  exit (EXIT_SUCCESS);
	  break;

	default:
	  usage (EXIT_FAILURE);
	}
    }

  if (verbose_flag == 1)
    printf ("Verbose mode...\n");

  if (argc == 2 + optind)
    {

      if (verbose_flag == 1)
	printf ("Reading directory %s...\n", argv[optind]);
      if (listdir (argv[optind], &l_list, &l_list_size) < 0)
	exit (EXIT_FAILURE);

      if (verbose_flag == 1)
	printf ("Reading directory %s...\n", argv[1 + optind]);
      if (listdir (argv[1 + optind], &r_list, &r_list_size) < 0)
	exit (EXIT_FAILURE);


      if (verbose_flag == 1)
	printf ("Sorting list of %d elements found in directory %s...\n",
		l_list_size, argv[optind]);
      qsort (l_list, l_list_size, sizeof (l_list[0]), intcmp);
      if (verbose_flag == 1)
	printf ("Sorting list of %d elements found in directory %s...\n",
		r_list_size, argv[1 + optind]);
      qsort (r_list, r_list_size, sizeof (r_list[0]), intcmp);


      if (verbose_flag == 1)
	printf ("Let merge procedure begin ...\n");
      mergeAll (l_list, r_list, l_list_size, r_list_size, argv[optind],
		argv[1 + optind], outdir);

    }
  else
    {
      usage (EXIT_FAILURE);
    }


  exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
